﻿#include "Lisp.h"

#include "Readline.h"
#include "Reader.h"
#include "Printer.h"
#include "Env.h"
#include "Util.h"
#include "StaticList.h"
#include "Exception.h"
#include "Core.h"

Lisp::ValPtr READ(const std::string &input);
Lisp::ValPtr EVAL(Lisp::ValPtr ast, Lisp::EnvPtr env);
std::string PRINT(Lisp::ValPtr val);
std::string Rep(const std::string &input, Lisp::EnvPtr env);

static void InstallLispFunctions(Lisp::EnvPtr env);

static Lisp::Readline s_readline;
static Lisp::EnvPtr s_repEnv(new Lisp::Env);

int main()
{
  Lisp::InitNativeBuiltIns(s_repEnv);
  InstallLispFunctions(s_repEnv);

  const std::string prompt = "user> ";
  std::string input;
  while (s_readline.Get(prompt, input))
  {
    std::string out = "";
    try
    {
      out = Rep(input, s_repEnv);
    }
    catch (const Lisp::EmptyInputException &e)
    {
      continue;
    }
    catch (const Lisp::RuntimeException &e)
    {
      out = "Runtime Error: " + e.What();
    }
    std::cout << out << std::endl;
  }
  return 0;
}

Lisp::ValPtr READ(const std::string &input)
{
  return Lisp::ReadStr(input);
}

Lisp::ValPtr EVAL(Lisp::ValPtr ast, Lisp::EnvPtr env)
{
  if (!env)
    env = s_repEnv;

  while (1)
  {

    Lisp::ListType *list = DYNAMIC_CAST(Lisp::ListType, ast);
    if (!list || (list->Size() == 0))
    {
      return ast->Eval(env);
    }

    if (const Lisp::SymbolType *symbol = DYNAMIC_CAST(Lisp::SymbolType, list->At(0)))
    {
      const std::string special = symbol->Value();
      const size_t argCount = list->Size() - 1;

      if (symbol->Value() == "def!")
      {
        Lisp::CheckArgsIs("def!", argCount, 2);
        const Lisp::SymbolType *symbol = VALUE_CAST(Lisp::SymbolType, list->At(1));
        return env->Set(symbol->Value(), EVAL(list->At(2), env));
      }
      else if (symbol->Value() == "let*")
      {
        Lisp::CheckArgsIs("let*", argCount, 2);
        const Lisp::SequenceType *bindings = VALUE_CAST(Lisp::SequenceType, list->At(1));
        const size_t count = Lisp::CheckArgsEven("let*", bindings->Size());
        Lisp::EnvPtr innerEnv(new Lisp::Env(env));
        for (size_t i = 0; i < count; i += 2)
        {
          const Lisp::SymbolType *symbol = VALUE_CAST(Lisp::SymbolType, bindings->At(i));
          innerEnv->Set(symbol->Value(), EVAL(bindings->At(i + 1), innerEnv));
        }
        ast = list->At(2);
        env = innerEnv;
        continue; // TCO
      }
      else if (symbol->Value() == "do")
      {
        Lisp::CheckArgsAtLeast("do", argCount, 1);

        for (int i = 1; i < argCount; i++)
        {
          EVAL(list->At(i), env);
        }
        ast = list->At(argCount);
        continue; // TCO
      }
      else if (symbol->Value() == "if")
      {
        Lisp::CheckArgsBetween("if", argCount, 2, 3);
        bool isTrue = EVAL(list->At(1), env)->IsTrue();
        if (!isTrue && (argCount == 2))
        {
          return Lisp::NilVal();
        }
        ast = list->At(isTrue ? 2 : 3);
        continue; // TCO
      }
      else if (symbol->Value() == "fn*")
      {
        Lisp::CheckArgsIs("fn*", argCount, 2);
        const Lisp::ListType *params = VALUE_CAST(Lisp::ListType, list->At(1));
        std::vector<std::string> paramNames;
        for (size_t i = 0; i < params->Size(); ++i)
        {
          const Lisp::SymbolType *symbol = VALUE_CAST(Lisp::SymbolType, params->At(i));
          paramNames.push_back(symbol->Value());
        }
        return Lisp::LambdaVal(paramNames, list->At(2), env);
      }
    }

    std::unique_ptr<Lisp::ValVec> items(list->EvalItems(env));

    Lisp::ValPtr op = items->at(0);
    if (const Lisp::LambdaType *lambda = DYNAMIC_CAST(Lisp::LambdaType, op))
    {
      Lisp::ListPtr args(new Lisp::ListType(items->begin() + 1, items->end()));
      return EVAL(lambda->GetBody(),
                  lambda->MakeEnv(args));
    }
    else
    {
      Lisp::ListPtr args(new Lisp::ListType(items->begin() + 1, items->end()));
      return APPLY(op, args);
    }
  }
}

Lisp::ValPtr APPLY(Lisp::ValPtr op, Lisp::ListPtr args)
{
  const Lisp::ApplicableType *handler = DYNAMIC_CAST(Lisp::ApplicableType, op);
  LISP_CHECK(handler != NULL,
             "\"%s\" is not applicable", op->ToString(true).c_str());

  return handler->Apply(args);
}

std::string PRINT(Lisp::ValPtr val)
{
  return Lisp::PrintStr(val);
}

std::string Rep(const std::string &input, Lisp::EnvPtr env)
{
  std::string res = PRINT(EVAL(READ(input), env));
  return res;
}

static const char *lispFunctionTable[] = {
    "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))",
    "(def! not (fn* (cond) (if cond false true)))",
    "(def! load-file (fn* (filename) \
        (eval (read-string (str \"(do \" (slurp filename) \"\nnil)\")))))",
    "(def! *host-language* \"C++\")",
};

static void InstallLispFunctions(Lisp::EnvPtr env)
{
  for (auto &function : lispFunctionTable)
  {
    Rep(function, env);
  }
}

Lisp::ValPtr Readline(const std::string &prompt)
{
  std::string input;
  if (s_readline.Get(prompt, input))
    return Lisp::StringVal(input);
  return Lisp::NilVal();
}